#include <mysql.h>
#include "cgzCLua.h"
#include "cgzCMysql.h"
#include "cgzCThreadPool.h"

static void decode(cgzCLua *that, const cgzITask *iTask, cgzCMysqlExec *exce)
{
    auto res = exce->m_reply;
    if (exce->m_err)
    {
        that->ocall(iTask->m_tref, "reply", {exce->m_cookid, false, exce->m_sort});
        return;
    }
    else if (!res)
    {
        that->ocall(iTask->m_tref, "reply", {exce->m_cookid, true, exce->m_sort});
        return;
    }
    // 遍历结果集
    clargs2 crows = {};

    // 添加字段名
    clargs afield = {};

    MYSQL_ROW row;
    int num_fields = mysql_num_fields(res);
    MYSQL_FIELD *fields = mysql_fetch_fields(res);
    for (int i = 0; i < num_fields; i++)
    {
        afield.push_back(fields[i].name);
    }

    while ((row = mysql_fetch_row(res)))
    {
        crows.push_back(std::move(clargs()));
        clargs &arow = crows.back();
        for (int i = 0; i < num_fields; i++)
        {
            if (nullptr == row[i])
            {
                arow.push_back((void *)nullptr);
                continue;
            }
            unsigned long *lengths = mysql_fetch_lengths(res);
            switch (fields[i].type)
            {
            case MYSQL_TYPE_NULL:
            {
                arow.push_back((void *)nullptr);
            }
            break;
            case MYSQL_TYPE_TINY:
            case MYSQL_TYPE_SHORT:
            case MYSQL_TYPE_LONG:
            case MYSQL_TYPE_LONGLONG:
            case MYSQL_TYPE_INT24:
            case MYSQL_TYPE_YEAR:
            case MYSQL_TYPE_BIT:
            case MYSQL_TYPE_BOOL:
            {
                if (row[i])
                {
                    arow.push_back(std::stol(row[i]));
                }
            }
            break;
            case MYSQL_TYPE_FLOAT:
            case MYSQL_TYPE_DOUBLE:
            case MYSQL_TYPE_DECIMAL:
            case MYSQL_TYPE_NEWDECIMAL:
            {
                if (row[i])
                {
                    arow.push_back(std::stod(row[i]));
                }
            }
            break;
            case MYSQL_TYPE_STRING:
            case MYSQL_TYPE_VARCHAR:
            case MYSQL_TYPE_VAR_STRING:
            case MYSQL_TYPE_DATE:
            case MYSQL_TYPE_TIME:
            case MYSQL_TYPE_DATETIME:
            case MYSQL_TYPE_NEWDATE:
            case MYSQL_TYPE_TIMESTAMP2:
            case MYSQL_TYPE_DATETIME2:
            case MYSQL_TYPE_TIME2:
            case MYSQL_TYPE_JSON:
            case MYSQL_TYPE_ENUM:
            {
                if (row[i])
                {
                    if (fields[i].type != MYSQL_TYPE_JSON)
                    {
                        arow.push_back({row[i], (int)lengths[i]});
                    }
                    else
                    {
                        arow.push_back({row[i], (int)lengths[i], egzl_jsonstr});
                    }
                }
            }
            break;
            case MYSQL_TYPE_TINY_BLOB:
            case MYSQL_TYPE_MEDIUM_BLOB:
            case MYSQL_TYPE_LONG_BLOB:
            case MYSQL_TYPE_BLOB:
            case MYSQL_TYPE_SET:
            {
                if (row[i])
                {
                    arow.push_back({row[i], (int)lengths[i]});
                }
            }
            break;
            default:
                break;
            }
        }
    }

    clargs args = {exce->m_cookid, afield, {crows, egzl_2array}};
    that->ocall(iTask->m_tref, "reply", args);
}

bool cgzCLua::run(const cgzITask *iTask, cgzCMysqlReply *reply)
{
    switch (reply->m_code)
    {
    case cgzMysqlCode::AUTH_SUCCESS:
    {
        ocall(iTask->m_tref, "auth", {});
    }
    break;
    case cgzMysqlCode::EXEC_REPLY:
    {
        ::decode(this, iTask, static_cast<cgzCMysqlExec *>(reply));
    }
    break;
    default:
    {
        cgz_this_error("unknown cmd tskName:{}", iTask->getName());
    }
    break;
    }
    return true;
}

static int mysql_new(lua_State *L)
{
    // 将其转换为 cgzCLua 指针
    cgzCLua *that = cgzCLua::getThat(L);
    const char *name = lua_tostring(L, 1);
    const char *ip = lua_tostring(L, 2);
    int port = lua_tointeger(L, 3);
    const char *db = lua_tostring(L, 4);
    const char *charset = lua_tostring(L, 5);
    const char *user = lua_tostring(L, 6);
    const char *password = lua_tostring(L, 7);
    int tref = luaL_ref(L, LUA_REGISTRYINDEX);
    void *node = malloc(sizeof(cgzCMysql));
    int cid = that->getId();
    auto cobj = new (node) cgzCMysql(that->m_pool, name, ip, user, password, port, db, charset, cid);
    cobj->m_tref = tref;
    return 0;
}

static void mysql_exec(lua_State *L, cgzCMysqlCmd cmd)
{
    // 将其转换为 cgzCLua 指针
    cgzCLua *that = cgzCLua::getThat(L);
    long cookid = lua_tointeger(L, 1);
    const char *name = lua_tostring(L, 2);
    const char *sql = lua_tostring(L, 3);
    void *node = malloc(sizeof(cgzCMysqlExec));
    auto ask = new (node) cgzCMysqlExec(cmd, sql, cookid);
    // 添加二进制参数
    int n = lua_gettop(L);
    for (int i = 4; i <= n; i++)
    {
        size_t len;
        const char *str = lua_tolstring(L, i, &len);
        ask->m_blobs.push_back({str, len});
    }
    that->regTask(name, ask);
}

static int mysql_insert(lua_State *L)
{
    mysql_exec(L, cgzCMysqlCmd::INSERT);
    return 0;
}

static int mysql_update(lua_State *L)
{
    mysql_exec(L, cgzCMysqlCmd::UPDATE);
    return 0;
}

static int mysql_delete(lua_State *L)
{
    mysql_exec(L, cgzCMysqlCmd::DELETE);
    return 0;
}

static int mysql_select(lua_State *L)
{
    mysql_exec(L, cgzCMysqlCmd::SELECT);
    return 0;
}

static int mysql_create(lua_State *L)
{
    mysql_exec(L, cgzCMysqlCmd::CREATE);
    return 0;
}

static int mysql_drop(lua_State *L)
{
    mysql_exec(L, cgzCMysqlCmd::DROP);
    return 0;
}

static int mysql_alter(lua_State *L)
{
    mysql_exec(L, cgzCMysqlCmd::ALTER);
    return 0;
}

static int mysql_truncate(lua_State *L)
{
    mysql_exec(L, cgzCMysqlCmd::TRUNCATE);
    return 0;
}

static int mysql_explain(lua_State *L)
{
    mysql_exec(L, cgzCMysqlCmd::EXPLAIN);
    return 0;
}

static int mysql_show(lua_State *L)
{
    mysql_exec(L, cgzCMysqlCmd::SHOW);
    return 0;
}

static int mysql_set(lua_State *L)
{
    mysql_exec(L, cgzCMysqlCmd::SET);
    return 0;
}

static int mysql_use(lua_State *L)
{
    mysql_exec(L, cgzCMysqlCmd::USE);
    return 0;
}

static int mysql_callback(lua_State *L)
{
    cgzCLua *that = cgzCLua::getThat(L);
    long cookid = lua_tointeger(L, 1);
    auto task = (cgzCMysqlExec *)that->m_retTask.back();
    assert(task->m_cookid == cookid);
    task->m_ret = true;
    return 0;
}

int cgzCLua::luaopen_mysql(lua_State *L)
{
    static const luaL_Reg otherlib[] = {
        {"new", mysql_new},
        {"insert", mysql_insert},
        {"update", mysql_update},
        {"delete", mysql_delete},
        {"select", mysql_select},
        {"create", mysql_create},
        {"drop", mysql_drop},
        {"alter", mysql_alter},
        {"truncate", mysql_truncate},
        {"explain", mysql_explain},
        {"show", mysql_show},
        {"set", mysql_set},
        {"use", mysql_use},
        {"callback", mysql_callback},
        {NULL, NULL}};

    luaL_newlib(L, otherlib);
    return 1;
}
